今天要來實踐如何在 Lazy Layout 中滑動 item。
昨天開心的研究完手勢互動,突然發現在 Lazy Grid Layout 中還沒有支援拖曳排序,於是乎我找了第三方開源庫 ComposeReorderable。
dependencies {
implementation("org.burnoutcrew.composereorderable:reorderable:<latest_version>")
}
他支援 Jetpack Compose 的 LazyList 和 LazyGrid 元件透過 modifier 擴充函數來做到 drag 和 drop 互動改變 list 的順序。
首先根據 ComposeReorderable 的範例,改寫如下。
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.burnoutcrew.reorderable.*
@Composable
fun LazyGridDragAndDrop() {
//顯示 card 的文字內容 index
val data = remember { mutableStateOf(List(100) { "Item $it" }) }
//state
val state = rememberReorderableLazyGridState(
dragCancelledAnimation = NoDragCancelledAnimation(),
onMove = { from, to ->
data.value = data.value.toMutableList().apply {
add(to.index, removeAt(from.index))
}
}
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
state = state.gridState,
modifier = Modifier.reorderable(state) //Liberary 做的 modifier extention
) {
items(data.value, { it }) { item ->
ReorderableItem(
state,
key = item,
defaultDraggingModifier = Modifier
) { isDragging ->
Card(
modifier = Modifier
.detectReorderAfterLongPress(state)
.aspectRatio(1f)
.padding(8.dp),
) {
Text(
text = item,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
}
}
}
}
從文章最開始的動圖可看看到,做出來的效果在垂直面上可以看到交換的效果,但水平面上卻會直接換過去。
為了解決這個問題我會想知道 Modifier.reorderable(state)裡面做了什麼,是不是有哪個步驟判斷手勢的位置去做了動畫效果。
他主要是在 modifier 做了extenstion funtion reorderable()
。裡頭利用了 Modifier
的 pointerInput()
。
fun Modifier.reorderable(
state: ReorderableState<*>
) = then(
Modifier.pointerInput(Unit) {
forEachGesture {
val dragStart = state.interactions.receive()
val down = awaitPointerEventScope {
currentEvent.changes.fastFirstOrNull { it.id == dragStart.id }
}
if (down != null && state.onDragStart(down.position.x.toInt(), down.position.y.toInt())) {
dragStart.offset?.apply {
state.onDrag(x.toInt(), y.toInt())
}
detectDrag(
down.id,
onDragEnd = {
state.onDragCanceled()
},
onDragCancel = {
state.onDragCanceled()
},
onDrag = { change, dragAmount ->
change.consume()
state.onDrag(dragAmount.x.toInt(), dragAmount.y.toInt())
})
}
}
})
這裡有些參數很類似昨天學的 interaction
,像是變數 dragStart
用到了 state.interactions
這裡回傳類型是庫中的 Channel<StartDrag>
在用 receive()
接收,這個寫法用到了 coroutines.channels
的概念。最後 dragStart
的類型是 StartDrag
。
detectDrag
追蹤Drag 的狀態。
在 onDrag
中用到了 change.consume()
是手勢中的消費手勢事件。
要能看懂這個三方庫需要懂 gesture
、interaction
、coroutines
、state
,全部研究出來可能就是另外 30天了… (頭髮都白了
今日運動:
休息